package com.jady.main;

import java.io.IOException;
import java.util.List;

import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.Field;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.LocalVariable;
import com.sun.jdi.Location;
import com.sun.jdi.Method;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StackFrame;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.event.BreakpointEvent;
import com.sun.jdi.event.ClassPrepareEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.MethodEntryEvent;
import com.sun.jdi.event.ModificationWatchpointEvent;
import com.sun.jdi.event.VMDeathEvent;
import com.sun.jdi.event.VMDisconnectEvent;
import com.sun.jdi.request.BreakpointRequest;
import com.sun.jdi.request.ClassPrepareRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.MethodEntryRequest;
import com.sun.jdi.request.ModificationWatchpointRequest;

public class FieldMonitor {

	public static final String CLASS_NAME = "com.jady.main.TestJad";
	public static final String FIELD_NAME = "foo";

	public static void main(String[] args) throws IOException,
			InterruptedException, AbsentInformationException,
			IncompatibleThreadStateException {
		// connect
		VirtualMachine vm = new VMAcquirer().connect(8000);

		// set watch field on already loaded classes
		List<ReferenceType> referenceTypes = vm.classesByName(CLASS_NAME);
		for (ReferenceType refType : referenceTypes) {
			addFieldWatch(vm, refType);
		}
		// watch for loaded classes
		addClassWatch(vm);

		// resume the vm
		vm.resume();

		// process events
		EventQueue eventQueue = vm.eventQueue();
		while (true) {
			EventSet eventSet = eventQueue.remove();

			for (Event event : eventSet) {
				if (event instanceof VMDeathEvent
						|| event instanceof VMDisconnectEvent) {
					// exit
					return;
				} else if (event instanceof ClassPrepareEvent) {
					// watch field on loaded class
					ClassPrepareEvent classPrepEvent = (ClassPrepareEvent) event;
					ReferenceType refType = classPrepEvent.referenceType();
					// System.out.println("Class refered: "+classPrepEvent.referenceType().getClass().getName());

					addFieldWatch(vm, refType);
				} else if (event instanceof ModificationWatchpointEvent) {
					// a Test.foo has changed
					ModificationWatchpointEvent modEvent = (ModificationWatchpointEvent) event;
					// Print the values being changed for foo
					// System.out.println("WatchModif Class refered: "+event.request().getClass().getName());
					// System.out.println("old="
					// + modEvent.valueCurrent());
					// System.out.println("new=" + modEvent.valueToBe());
					// System.out.println();
				} else if (event instanceof MethodEntryEvent) {

					MethodEntryEvent method = (MethodEntryEvent) event;
					Method method2 = method.method();
					if (method2.name().equals("main")) {
						List<String> names = method2.argumentTypeNames();
						System.out.println("");
						for (String string : names) {
							System.out.println("Parem: " + string);
						}
						List<LocalVariable> arguments = method2.arguments();
						for (LocalVariable localVariable : arguments) {
							System.out.println("VarName: "
									+ localVariable.name());
						}
						System.out.println("");
					}

					if (method2.isConstructor()) {
						List<String> names = method2.argumentTypeNames();
						System.out.println("Name: "
								+ method2.getClass().getName() + ":"
								+ method2.name());
						for (String string : names) {
							System.out.println("Parem: " + string);
						}
					}

				} else if (event instanceof BreakpointEvent) {
					BreakpointEvent brkPntEvnt = (BreakpointEvent) event;
					System.out.println("");
					ThreadReference thread = brkPntEvnt.thread();

					List<StackFrame> frames = thread.frames();
					for (StackFrame stackFrame : frames) { // printing the stack
															// of at the
															// breakpoint
						System.out.println("FrameToString:"
								+ stackFrame.location() + "; "
								+ stackFrame.location().sourcePath());
					}
				}
			}
			eventSet.resume();
		}
	}

	/** Watch all classes of name "Test" */
	private static void addClassWatch(VirtualMachine vm) {
		EventRequestManager erm = vm.eventRequestManager();
		ClassPrepareRequest classPrepareRequest = erm
				.createClassPrepareRequest();
		classPrepareRequest.addClassFilter(CLASS_NAME);
		classPrepareRequest.setEnabled(true);
	}

	/**
	 * Watch field of name "foo". Although the method name is not appropriate this also creates a breakpoint request.
	 * 
	 * @throws AbsentInformationException
	 */
	private static void addFieldWatch(VirtualMachine vm, ReferenceType refType)
			throws AbsentInformationException {
		EventRequestManager erm = vm.eventRequestManager();
		Field field = refType.fieldByName(FIELD_NAME);

		List<Method> methods = refType.methods();

		MethodEntryRequest methEntReq = erm.createMethodEntryRequest();
		methEntReq.addClassFilter(CLASS_NAME);
		methEntReq.setEnabled(true);

		List<Location> locationsOfLine = refType.locationsOfLine(null,
				"TestJad.java", 22);

		BreakpointRequest brkPntEvntReq = erm
				.createBreakpointRequest(locationsOfLine.get(0));
		brkPntEvntReq.setEnabled(true);

		// Creating the method entry request. Looping to display the method of
		// the debugee(TestJad)
		// for (Method method : methods) {
		// System.out.println(method.name());
		// if(method.name().equals("main")) {
		// MethodEntryRequest createMethodEntryRequest =
		// erm.createMethodEntryRequest();
		// createMethodEntryRequest.setEnabled(true);
		// }
		// // if(method.isConstructor()) {
		// // MethodEntryRequest createMethodEntryRequest =
		// erm.createMethodEntryRequest();
		// // createMethodEntryRequest.setEnabled(true);
		// // }
		// }

		ModificationWatchpointRequest modificationWatchpointRequest = erm
				.createModificationWatchpointRequest(field);
		modificationWatchpointRequest.setEnabled(true);
	}

}
